//cGFXBuf.cpp
#include "cGFXBuf.h"

const u32 GFXBuf::TileSize = 32; //bytes per tile

/* Default constructor. Defaults to 1024 tiles.
 */
GFXBuf::GFXBuf()
{
	this->_Create(1024);
}

/* Constructor # tiles.
 */
GFXBuf::GFXBuf(u32 NumTiles)
{
	this->_Create(NumTiles);
}

/* PROTECTED: Internal constructor routine.
 * Inputs:
 * -NumTiles: The number of tiles the buffer will hold.
 */
void GFXBuf::_Create(u32 NumTiles)
{
	this->NumTiles = NumTiles;
	this->Buf = (u8*)malloc(GFXBuf::TileSize * NumTiles);
	if(!this->Buf) throw new std::bad_alloc;
	
	//TEST
	/*FILE *File = fopen("GFX00.bin", "rb");
	if(!File)
	{
		DebugOut(DO_MAJOR, "Couldn't open GFX00\n");
		return;
	}
	
	if(!fread(this->Buf, 1, 4096, File))
		DebugOut(DO_MAJOR, "Couldn't read GFX00\n");
	
	DebugOut(DO_STATUS, "Read GFX00\n");
	fclose(File);*/
}

/* Destructor.
 */
GFXBuf::~GFXBuf()
{
	if(this->Buf) free(this->Buf); this->Buf = NULL;
}

/* Retrieves the buffer.
 * Returns: obvious.
 */
u8* GFXBuf::GetBuffer()
{
	return this->Buf;
}

/* Retrieves the number of tiles the buffer can hold.
 * Returns: obvious.
 */
u32 GFXBuf::GetNumTiles()
{
	return this->NumTiles;
}

/* Loads graphics from a file, converting to 4BPP if necessary.
 * Inputs:
 * -Filename: File to load from.
 * -Addr: Address of graphics.
 * -NumTiles: Number of tiles to load.
 * -DestTile: Tile number in the buffer to load to.
 * -Compression: The compression format used.
 * Returns: Number of tiles loaded.
 */
u32 GFXBuf::LoadFromFile(std::string Filename, u32 Addr, u32 NumTiles,
u32 DestTile, CompressionFormat Compression)
{
	u32 Ret;
	FILE *File = fopen(Filename.c_str(), "rb");
	if(!File) return 0;
	Ret = this->LoadFromFile(File, Addr, NumTiles, DestTile, Compression);
	fclose(File);
	return Ret;
}

u32 GFXBuf::LoadFromFile(FILE *File, u32 Addr, u32 NumTiles,
u32 DestTile, CompressionFormat Compression)
{
	bool Is3BPP = false;
	u32 NumBytes = 0;
	CompressorLcLz2 CLZ;
	Compressor3BPP C3BPP;
	u8 *NewBuf;
	
	//return 512; //TEST
	if(NumTiles > (this->NumTiles - DestTile))
		NumTiles = this->NumTiles - DestTile;
	
	DebugOut(DO_STATUS, "Loading %u tiles in format %u from 0x%06X... ",
		NumTiles, Compression, Addr);
	switch(Compression)
	{
		case COMP_3BPP:
			Is3BPP = true;
		//fall through
		
		case COMP_NONE:
			fseek(File, Addr, SEEK_SET);
			NumBytes = fread(&this->Buf[DestTile * GFXBuf::TileSize], 1,
				GFXBuf::TileSize * NumTiles, File);
		break;
		
		case COMP_3BPP_LZ2:
			Is3BPP = true;
		//fall through
		
		case COMP_LZ2:
			try {
				NumBytes = CLZ.Decompress(File, Addr,
					&this->Buf[DestTile * GFXBuf::TileSize],
					GFXBuf::TileSize * NumTiles);
			} catch(InvalidDataException *e) {
				DebugOut(DO_MINOR, "Decompression failed! File is corrupt.\n");
				delete e;
				return 0;
			};
		break;
		
		default: throw new UnimplementedException(); break;
	}
	
	DebugOut(DO_STATUS, "Done.\n");
	
	if(Is3BPP) //Convert to 4BPP
	{
		NewBuf = (u8*)malloc(this->NumTiles * GFXBuf::TileSize);
		if(!NewBuf)
		{
			DebugOut(DO_MINOR, "Failed to allocate buffer for 3BPP -> 4BPP.\n");
			return 0;
		}
		
		NumBytes = C3BPP.Decompress(&this->Buf[DestTile * GFXBuf::TileSize],
			NewBuf, NumTiles * GFXBuf::TileSize);
		
		memcpy(&this->Buf[DestTile * GFXBuf::TileSize], NewBuf,
			NumTiles * GFXBuf::TileSize);
		free(NewBuf);
	}
	
	//FILE *out = fopen("dump.bin", "wb");
	//fwrite(this->Buf, 1, NumBytes, out);
	//fclose(out);
	
	if(NumBytes < (NumTiles * GFXBuf::TileSize))
		DebugOut(DO_MINOR,
			"GFXBuf: Only %u of %u bytes were loaded from 0x%06X\n", NumBytes,
			NumTiles * GFXBuf::TileSize, Addr);
	return NumBytes / GFXBuf::TileSize;
}

/* Loads graphics from ROM, converting to 4BPP if necessary.
 * Inputs:
 * -ROM: File to load from.
 * -Addr: Address of graphics.
 * -NumTiles: Number of tiles to load.
 * -DestTile: Tile number in the buffer to load to.
 * -Compression: The compression format used.
 * Returns: Number of tiles loaded.
 */
u32 GFXBuf::LoadFromROM(ROMFile *ROM, u32 Addr, u32 NumTiles,
u32 DestTile, CompressionFormat Compression)
{
	return this->LoadFromFile(ROM->File, Addr, NumTiles, DestTile, Compression);
}

u32 GFXBuf::LoadFromROM(ROMFile *ROM, SNESPointer *Ptr, u32 NumTiles,
u32 DestTile, CompressionFormat Compression)
{
	return this->LoadFromFile(ROM->File, Ptr->ToFileAddr(), NumTiles, DestTile,
		Compression);
}


/* Renders a tile in the specified palette.
 * Inputs:
 * -Tile: Tile number.
 * -Pal: Pointer to palette (16 SNES colours).
 * -Dest: Destination buffer.
 * -DX, DY: Destination coords.
 * -Transparent: If true, treat colour 0 as transparent.
 * Returns: true on success, false on failure.
 */
bool GFXBuf::RenderTile(u32 Tile, u16 *Pal, ImgBuf *Dest, u32 DX, u32 DY,
bool Transparent, bool XFlip, bool YFlip)
{
	u8 Col;
	ImgBuf::Pixel *Pixels;
	u32 DW, DH, x, y;
	
	if(Tile >= this->NumTiles) return false;
	
	Pixels = Dest->GetPixels();
	if(!Pixels) return false;
	DW = Dest->GetWidth();
	DH = Dest->GetHeight();
	
	for(u32 Y=0; Y<8; Y++)
	{
		for(u32 X=0; X<8; X++)
		{
			x = (XFlip ? X : (7 - X)) + DX;
			y = (YFlip ? (7 - Y) : Y) + DY;
			
			if((x >= DW) || (y >= DH)) continue;
			
			Col = ((this->Buf[(32 * Tile) + (2 * Y)] & BIT(X)) >> X)
			| (((this->Buf[(32 * Tile) + (2 * Y) + 1] & BIT(X)) >> X) << 1)
			| (((this->Buf[(32 * Tile) + (2 * Y) + 16] & BIT(X)) >> X) << 2)
			| (((this->Buf[(32 * Tile) + (2 * Y) + 17] & BIT(X)) >> X) << 3);
			if(Transparent && !Col) continue;
			
			Pixels[(y * DW) + x].Red = BITS(Pal[Col], 0, 4) << 3;
			Pixels[(y * DW) + x].Green = BITS(Pal[Col], 5, 9) << 3;
			Pixels[(y * DW) + x].Blue = BITS(Pal[Col], 10, 14) << 3;
		}
	}
	
	return true;
}

//convenience wrapper
bool GFXBuf::RenderTile(u32 Tile, u16 *Pal, ImgBuf *Dest, u32 DX, u32 DY,
bool Transparent)
{
	return this->RenderTile(Tile, Pal, Dest, DX, DY, Transparent, false, false);
}

/* Renders a tile given raw tile/attribute data.
 * Inputs:
 * -Tile: Tile data.
 * -Pal: Pointer to palette (128 SNES colours).
 * -Dest: Destination buffer.
 * -DX, DY: Destination coords.
 * -Transparent: If true, treat colour 0 as transparent.
 * Returns: true on success, false on failure.
 * Notes:
 * -Since the attributes include the palette number, this takes a pointer to 128
 *  colours (i.e. 8 palettes) instead of 16.
 */
bool GFXBuf::RenderRawTile(u16 Tile, u16 *Pal, ImgBuf *Dest, u32 DX, u32 DY,
bool Transparent)
{
	u32 ID = TILE_ID(Tile);
	u32 PalNum = TILE_PALETTE(Tile);
	//bool Priority = TILE_PRIORITY(Tile);
	bool XFlip = TILE_XFLIP(Tile), YFlip = TILE_YFLIP(Tile);
	return this->RenderTile(ID, &Pal[PalNum * 16], Dest, DX, DY, Transparent,
		XFlip, YFlip);
}

/* Renders a magnified tile in the specified palette.
 * Inputs:
 * -Tile: Tile number.
 * -Pal: Pointer to palette (16 SNES colours).
 * -Dest: Destination buffer.
 * -DX, DY: Destination coords.
 * -Transparent: If true, treat colour 0 as transparent.
 * -Mag: Number of times to magnify.
 * Returns: true on success, false on failure.
 */
bool GFXBuf::RenderMagnifiedTile(u32 Tile, u16 *Pal, ImgBuf *Dest, u32 DX,
u32 DY, bool Transparent, bool XFlip, bool YFlip, u32 Mag)
{
	u8 Col;
	if(Tile >= this->NumTiles) return false;
	if(Mag < 2) return this->RenderTile(Tile, Pal, Dest, DX, DY, Transparent,
		XFlip, YFlip);
	
	for(u32 Y=0; Y<8; Y++)
	{
		for(u32 X=0; X<8; X++)
		{
			Col = ((this->Buf[(32 * Tile) + (2 * Y)] & BIT(X)) >> X)
			| (((this->Buf[(32 * Tile) + (2 * Y) + 1] & BIT(X)) >> X) << 1)
			| (((this->Buf[(32 * Tile) + (2 * Y) + 16] & BIT(X)) >> X) << 2)
			| (((this->Buf[(32 * Tile) + (2 * Y) + 17] & BIT(X)) >> X) << 3);
			if(Transparent && !Col) continue;
			
			Dest->SetFGColour(BITS(Pal[Col], 0, 4) << 3,
				BITS(Pal[Col], 5, 9) << 3, BITS(Pal[Col], 10, 14) << 3);
			
			Dest->DrawRectangle(
				 ((XFlip ? X : (7-X))+DX)*Mag, ((YFlip ? (7-Y) : Y)+DY)*Mag,
				 Mag, Mag, true);
		}
	}
	
	return true;
}

//convenience wrapper
bool GFXBuf::RenderMagnifiedTile(u32 Tile, u16 *Pal, ImgBuf *Dest, u32 DX,
u32 DY, bool Transparent, u32 Mag)
{
	return this->RenderMagnifiedTile(Tile, Pal, Dest, DX, DY, Transparent,
		false, false, Mag);
}

/* Renders a magnified tile given raw tile/attribute data.
 * Inputs:
 * -Tile: Tile data.
 * -Pal: Pointer to palette (128 SNES colours).
 * -Dest: Destination buffer.
 * -DX, DY: Destination coords.
 * -Transparent: If true, treat colour 0 as transparent.
 * -Mag: Number of times to magnify.
 * Returns: true on success, false on failure.
 * Notes:
 * -Since the attributes include the palette number, this takes a pointer to 128
 *  colours (i.e. 8 palettes) instead of 16.
 */
bool GFXBuf::RenderMagnifiedRawTile(u16 Tile, u16 *Pal, ImgBuf *Dest, u32 DX,
u32 DY, bool Transparent, u32 Mag)
{
	u32 ID = TILE_ID(Tile);
	u32 PalNum = TILE_PALETTE(Tile);
	//bool Priority = TILE_PRIORITY(Tile);
	bool XFlip = TILE_XFLIP(Tile), YFlip = TILE_YFLIP(Tile);
	return this->RenderMagnifiedTile(ID, &Pal[PalNum * 16], Dest, DX, DY,
		Transparent, XFlip, YFlip, Mag);
}

/* Copies tiles to another GFXBuf.
 * Inputs:
 * -Tile: Tile number to copy.
 * -Dest: Buffer to copy to.
 * -DestTile: Tile number to overwrite.
 * -NumTiles: Number of tiles to copy.
 * Returns: true on success, false on failure.
 */
bool GFXBuf::CopyTiles(u32 Tile, GFXBuf *Dest, u32 DestTile, u32 NumTiles)
{
	if(!Dest || !Dest->Buf) return false;
	memcpy(&Dest->Buf[GFXBuf::TileSize * DestTile],
		&this->Buf[GFXBuf::TileSize * Tile],
		GFXBuf::TileSize * NumTiles);
	return true;
}
